pytorch, transformersを使ったBERTのfine-tuningの方法
これが日本語だと最も参考になった。
これの参考文献
コード見ると分かるが、上の日本語の記事内のコードはこれを参照している
これを読むと良い
これはわからんが後で読む
日本語記事になくて、英語の元記事にあるところを追加で理解する
get_linear_schedule_with_warmup
optimizerのschedulerまわりの知識がまだ少ない
learning rateとかをstepに応じて変化させる云々らしい
code:py
from transformers import get_linear_schedule_with_warmup
# Number of training epochs. The BERT authors recommend between 2 and 4.
# We chose to run for 4, but we'll see later that this may be over-fitting the
# training data.
epochs = 4
# (Note that this is not the same as the number of training samples).
total_steps = len(train_dataloader) * epochs
# Create the learning rate scheduler.
scheduler = get_linear_schedule_with_warmup(optimizer,
num_warmup_steps = 0, # Default value in run_glue.py
num_training_steps = total_steps)
model の保存周りがよく分かってない
以下のような条件で
素直にpytorchを使う
pytorchのフレームワーク等は使わない( pytorch lightning等)
ある程度雑なのは一旦許容する
抽象の漏れ等
必要なオブジェクト
model
optimizer
dataloader
tokenizer
https://gyazo.com/ee770a46d5668dd9f60f0c6218705a20
すげえ雑に全体像を描くとこうなる
factory.pyの中に、
def get_model:
def get_optimizers:
という形でファクトリ関数を用意して、ここから上記のオブジェクトを作成する
学習のサイクル自体はfitという関数にまとめる
以下を引数で入力する
model,
dataloader(trainとvalid)
optimizer
epoch数
code:py
def fit(model, train_dl, valid_dl, optimizer, epochs):
# 実際は経過時間を計測するためにforあたりで時間測る処理が挟まる
model.to(device)
for epoch in range(epochs):
train(model, train_dl, optimizer)
validation(model, valid_dl)
# 精度やかかった時間などの結果をdictにしてreturnする
# trainとvalidationはほとんど同じ
# optimizerを使っているかどうか
# モデルにbatchを渡して損失関数の結果を得るあたりはbatch_lossという関数にまとめている
def train(model, dl, optim):
model.train()
total_loss = 0
for batch in dl:
loss,_ batch_loss(model, batch, optim)
total_loss += loss
return total_loss
def validation(model, dl):
model.eval()
total_loss = 0
total_accuracy = 0
with torch.no_grad():
loss, logits = batch_loss(model, batch)
total_loss += loss
# logitsから確率最大のものを予測結果として精度を計算 (略)
return total_loss, total_accuracy
batch_lossはtrainとvalidationで共通の「モデルにバッチを送って結果を得る」部分を行う。
最後の引数のoptimizerの有無に応じて、optimizerで重みを更新を実行する
https://gyazo.com/8be3b7d1de9cc3ede2dead8fef2c6aa4
入力データを用意して、
文章をtokenizeして、
dataloaderに変換して
もろもろをfitに渡す
modelとtokenizer
transformerを使うと
code:py
def get_model(num_labels):
model = BertForSequenceClassification.from_pretrained(
'cl-tohoku/bert-base-japanese-whole-word-masking',
num_labels=num_labels,
output_attentions = False,
output_hidden_states = False,
)
return model
これで東北大学のpretrainedモデルが入る
BERTにおいてはtokenizerは事前学習に用いたものと同じものを使う必要がある
code:py
def get_tokenizer():
return BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
同様にfrom_pretrainedで取得する
optimizer
code:py
def get_optimizers(model):
return AdamW(model.parameters(), lr=2e-5)
optimizerあたりはschedulerがBERTには必要
必要というか、元論文が使ってるので
dataloader
今回は雑にやった
第一引数に、TensorDatasetにわたすものを渡す
第2引数に、batch size
trainとvalidationの分割周りは抽象されておらず、9:1で決めうってしまった
torch.utils.dataのrandom_split使った
code:py
def get_dataloader(ds_source, batch_size):
"""source will be passed TensorDataset"""
train_ds, valid_ds = _get_datasets(*ds_source)
train_dl = DataLoader(
train_ds,
sampler = RandomSampler(train_ds),
batch_size = batch_size
)
valid_dl = DataLoader(
valid_ds,
sampler = SequentialSampler(valid_ds),
batch_size = batch_size
)
return train_dl, valid_dl
def _get_datasets(*args):
dataset = TensorDataset(*args)
# ここらへんもっときれいにできそう
# 90%地点のIDを取得
train_size = int(0.9 * len(dataset))
val_size = len(dataset) - train_size
# データセットを分割
return train_ds, val_ds
データ分割ってどんな手段があるんだろう?
比率とrandom_split以外で
分割方法等を抽象するならcallbackで操作させるとかはあるが…
get_datasetsのところは可変長引数でtensordatasetにわたす
これはbatchのタプルを展開するときに関わる
暗黙のインターフェースが発生する
ので適切な抽象化ではない